package com.yuke.cloud.service.uac.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.arronlong.httpclientutil.HttpClientUtil;
import com.arronlong.httpclientutil.common.HttpConfig;
import com.arronlong.httpclientutil.common.HttpHeader;
import com.arronlong.httpclientutil.exception.HttpProcessException;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.yuke.cloud.common.base.dto.LoginAuthDto;
import com.yuke.cloud.common.base.dto.UserTokenDto;
import com.yuke.cloud.common.base.enums.ErrorCodeEnum;
import com.yuke.cloud.common.core.enums.LogTypeEnum;
import com.yuke.cloud.common.core.utils.RequestUtil;
import com.yuke.cloud.common.security.auth.app.AppSecretException;
import com.yuke.cloud.common.security.core.SecurityCoreConfig;
import com.yuke.cloud.common.security.core.properties.OAuth2ClientProperties;
import com.yuke.cloud.common.security.core.properties.SecurityProperties;
import com.yuke.cloud.common.util.PublicUtil;
import com.yuke.cloud.common.util.RedisKeyUtil;
import com.yuke.cloud.service.api.uac.dto.UserLoginInfoDto;
import com.yuke.cloud.service.api.uac.exceptions.UacBizException;
import com.yuke.cloud.service.uac.dao.AccUserTokenMapper;
import com.yuke.cloud.service.uac.dto.TokenMainQueryDto;
import com.yuke.cloud.service.uac.entity.AccLog;
import com.yuke.cloud.service.uac.entity.AccPermission;
import com.yuke.cloud.service.uac.entity.AccUser;
import com.yuke.cloud.service.uac.entity.AccUserToken;
import com.yuke.cloud.service.uac.enums.AccUserTokenStatusEnum;
import com.yuke.cloud.service.uac.service.AccPermissionService;
import com.yuke.cloud.service.uac.service.AccUserService;
import com.yuke.cloud.service.uac.service.AccUserTokenService;
import com.yuke.cloud.common.core.support.BaseService;
import com.yuke.cloud.service.uac.service.OpcRpcService;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.modelmapper.ModelMapper;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * Created by wg on 2018/09/11.
 */
@Service
@ComponentScan(basePackageClasses={SecurityCoreConfig.class})
@Transactional
@Slf4j
public class AccUserTokenServiceImpl extends BaseService<AccUserToken> implements AccUserTokenService {
    @Resource
    private AccUserTokenMapper accUserTokenMapper;

    @Resource
    private AccUserService accUserService;
    @Autowired
    private SecurityProperties securityProperties;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private OpcRpcService opcRpcService;
    @Value("${yuke.auth.refresh-token-url}")
    private String refreshTokenUrl;

    @Resource
    private AccPermissionService accPermissionService;  //add by wg 20190126

    @Override
    public void saveUserToken(String accessToken, String refreshToken, LoginAuthDto loginAuthDto, HttpServletRequest request, Boolean updateFlag, String oldAccessToken) {
        // 获取登录时间
        log.info("saveUserToken---loginAuthDto=:{}",loginAuthDto);
        Long userId = loginAuthDto.getUserId();
        AccUser accUser = accUserService.selectByKey(userId);
        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //获取客户端操作系统
        final String os = userAgent.getOperatingSystem().getName();
        //获取客户端浏览器
        final String browser = userAgent.getBrowser().getName();
        final String remoteAddr = RequestUtil.getRemoteAddr(request);
        // 根据IP获取位置信息
        final String remoteLocation = opcRpcService.getLocationById(remoteAddr);

        // 存入mysql数据库
        AccUserToken accUserToken = new AccUserToken();
        OAuth2ClientProperties[] clients = securityProperties.getOauth2().getClients();
        int accessTokenValidateSeconds = clients[0].getAccessTokenValidateSeconds();
        int refreshTokenValiditySeconds = clients[0].getRefreshTokenValiditySeconds();
        accUserToken.setOs(os);
        accUserToken.setBrowser(browser);
        accUserToken.setAccessToken(accessToken);
        accUserToken.setAccessTokenValidity(accessTokenValidateSeconds);
        accUserToken.setLoginIp(remoteAddr);
        if (remoteLocation == null) {
            accUserToken.setLoginLocation("");
        }else {
            accUserToken.setLoginLocation(remoteLocation);
        }

        // mod by wg 20181224 改成当前时间
//        accUserToken.setLoginTime(accUser.getLastLoginTime());
        if (!updateFlag) {
            accUserToken.setLoginTime(new Date());
        }

        accUserToken.setLoginName(loginAuthDto.getLoginName());
        accUserToken.setRefreshToken(refreshToken);
        accUserToken.setRefreshTokenValidity(refreshTokenValiditySeconds);
        accUserToken.setStatus(AccUserTokenStatusEnum.ON_LINE.getStatus());
        accUserToken.setUserId(userId);
        accUserToken.setUserName(loginAuthDto.getUserName());

        if (loginAuthDto.getGroupId() == null) {
            accUserToken.setGroupId(0L);
        }else {
            accUserToken.setGroupId(loginAuthDto.getGroupId());
        }

        if (loginAuthDto.getGroupName() == null) {
            accUserToken.setGroupName("");
        }else {
            accUserToken.setGroupName(loginAuthDto.getGroupName());
        }

        if (!updateFlag) {
            accUserToken.setTokenId(generateId());
            accUserToken.setCreateTime(new Date());
        }
        accUserToken.setUpdateTime(new Date());
        log.info("====getAccessToken:{}", accUserToken.getAccessToken());
        log.info("++++getRefreshToken:{}", accUserToken.getRefreshToken());
        log.info("-----oldAccessToken:{}",oldAccessToken);
        // add by wg 20181224 获取登录时的deviceId
        final String deviceId = request.getHeader("deviceId");
        if (!org.apache.commons.lang.StringUtils.isBlank(deviceId)) {
            accUserToken.setDeviceId(deviceId);
        }

        // mod by wg 20181224 改成登录和token刷新两种情况
        if (!updateFlag) {
            //登录时
            accUserTokenMapper.insertSelective(accUserToken);
        }else {
            // 更新数据库
            if (PublicUtil.isEmpty(oldAccessToken)) {
                log.error("这里的旧token为空");
                throw new UacBizException(ErrorCodeEnum.GL99990100);
            }

            Example example = new Example(AccUserToken.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("accessToken", oldAccessToken);

            // add for debug 20190305
            log.info("需要刷新Token--------oldAccessToken={}, newAccessToken={}, newRefreshToken={}", oldAccessToken, accessToken, refreshToken);

            AccUserToken userTokenTemp = accUserTokenMapper.selectOneByExample(example);
            if (PublicUtil.isNotEmpty(userTokenTemp)) {  // mod by wg 20190110 对不存在的access_token进行处理
                accUserToken.setLoginTime(userTokenTemp.getLoginTime());
                accUserToken.setCreateTime(userTokenTemp.getCreateTime());
                accUserToken.setPid(userTokenTemp.getPid());
                accUserToken.setStatus(AccUserTokenStatusEnum.ON_REFRESH.getStatus());

                accUserTokenMapper.updateByExampleSelective(accUserToken, example);

                // 删除旧的redis token缓存
                redisTemplate.delete(RedisKeyUtil.getAccessTokenKey(oldAccessToken));//根据key删除缓存

                // 删除旧token记录，必须在创建刷新token后执行
//            this.deleteUacUserToken(accessToken);
            }else {
                log.info("用户ID="+accUser.getUserId());
                log.info("用户：{}    的token刷新没找到",accUser.getUserName());
                log.error("旧token到数据库查找没找到");
                //throw new UacBizException(ErrorCodeEnum.UAC10011039);
                Example exam = new Example(AccUserToken.class);
                Example.Criteria crit = exam.createCriteria();
                exam.setOrderByClause("token_id desc");
                crit.andEqualTo("userId", accUser.getUserId());
                List<AccUserToken> accUserTokenList = accUserTokenMapper.selectByExample(exam);

                if (accUserTokenList == null || accUserTokenList.size() < 1){
                    log.error("旧token到数据库查找没找到,userId也未找到");
                    throw new UacBizException(ErrorCodeEnum.UAC10011039);
                }

                userTokenTemp = accUserTokenList.get(0);
                accUserToken.setLoginTime(userTokenTemp.getLoginTime());
                accUserToken.setCreateTime(userTokenTemp.getCreateTime());
                accUserToken.setPid(userTokenTemp.getPid());
                accUserToken.setStatus(AccUserTokenStatusEnum.ON_REFRESH.getStatus());
                accUserTokenMapper.updateByExampleSelective(accUserToken, example);
                redisTemplate.delete(RedisKeyUtil.getAccessTokenKey(oldAccessToken));

            }
        }

        // 存入redis数据库
        UserTokenDto userTokenDto = new ModelMapper().map(accUserToken, UserTokenDto.class);
        updateRedisUserToken(accessToken, accessTokenValidateSeconds, userTokenDto);
    }

    private void updateRedisUserToken(String accessToken, int accessTokenValidateSeconds, UserTokenDto userTokenDto) {
        // mod by wg 20181214 当退出登录时，删除redis中的token
        if (AccUserTokenStatusEnum.OFF_LINE.getStatus() == userTokenDto.getStatus()) {
            redisTemplate.delete(RedisKeyUtil.getAccessTokenKey(accessToken));//根据key删除缓存
        }else {
            //add by wg 20190126 将用户的API权限也缓存到redis中 start
            List<AccPermission> ownAuthList = accPermissionService.getOwnPermissionListByUserId(userTokenDto.getUserId());
            List<String> listTemp = new ArrayList<>();
            for (AccPermission permission : ownAuthList) {
                listTemp.add(permission.getUrl());
                //log.info("用户的API权限:{}",permission.getUrl());
            }
            userTokenDto.setAuthorityUrlList(listTemp);
            //add by wg 20190126 将用户的API权限也缓存到redis中 end
            log.info("AccUserTokenServiceImpl:保存userTokenDto到Redis");
            redisTemplate.opsForValue().set(RedisKeyUtil.getAccessTokenKey(accessToken), userTokenDto, accessTokenValidateSeconds, TimeUnit.SECONDS);
        }
    }

    @Override
    public UserTokenDto getByAccessToken(String accessToken) {
        UserTokenDto userTokenDto = (UserTokenDto) redisTemplate.opsForValue().get(RedisKeyUtil.getAccessTokenKey(accessToken));
        if (userTokenDto == null) {
            AccUserToken accUserToken = new AccUserToken();
            accUserToken.setAccessToken(accessToken);
            accUserToken = accUserTokenMapper.selectOne(accUserToken);
            userTokenDto = new ModelMapper().map(accUserToken, UserTokenDto.class);
        }
        return userTokenDto;
    }

    @Override
    public void updateUacUserToken(UserTokenDto tokenDto, LoginAuthDto loginAuthDto) {
        AccUserToken accUserToken = new ModelMapper().map(tokenDto, AccUserToken.class);
        accUserToken.setUpdateTime(new Date());
        accUserTokenMapper.updateByPrimaryKeySelective(accUserToken);
        OAuth2ClientProperties[] clients = securityProperties.getOauth2().getClients();
        int accessTokenValidateSeconds = clients[0].getAccessTokenValidateSeconds();
        updateRedisUserToken(accUserToken.getAccessToken(), accessTokenValidateSeconds, tokenDto);
    }

    @Override
    public void deleteUacUserToken(String accessToken) {
        Example example = new Example(AccUserToken.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("accessToken", accessToken);
        accUserTokenMapper.deleteByExample(example);

        redisTemplate.delete(RedisKeyUtil.getAccessTokenKey(accessToken));//根据key删除缓存

        // 记录操作日志
//        AccLog log = new AccLog();
//        log.setGroupId(principal.getGroupId());
//        log.setGroupName(principal.getGroupName());
//        log.setIp(remoteAddr);
//        log.setLocation(remoteLocation);
//        log.setOs(os);
//        log.setBrowser(browser);
//        log.setRequestUrl(requestURI);
//        log.setLogType(LogTypeEnum.LOGOUT_LOG.getType());
//        log.setLogName(LogTypeEnum.LOGOUT_LOG.getName());
//
//        taskExecutor.execute(() -> accLogService.saveLog(log, loginAuthDto));
    }

    @Override
    public PageInfo listTokenWithPage(TokenMainQueryDto token) {
        PageHelper.startPage(token.getPageNum(), token.getPageSize());
        AccUserToken userToken = new AccUserToken();
        userToken.setStatus(token.getStatus());
        if (token.getStatus() != null) {
            userToken.setStatus(token.getStatus());
        }

        if (StringUtils.isNotBlank(token.getLoginName())) {
            userToken.setLoginName(token.getLoginName());
        }
        if (StringUtils.isNotBlank(token.getUserName())) {
            userToken.setUserName(token.getUserName());
        }
        List<AccUserToken> userTokenList = accUserTokenMapper.selectTokenList(userToken);
        return new PageInfo<>(userTokenList);
    }

    @Override
    public String refreshToken(String accessToken, String refreshToken, HttpServletRequest request) throws HttpProcessException {
        String token;
        Map<String, Object> map = new HashMap<>(2);
        map.put("grant_type", "refresh_token");
        map.put("refresh_token", refreshToken);

        // add for debug 20190305
        log.info("--------oldAccessToken={}, oldRefreshToken={}", accessToken, refreshToken);
        System.out.println("accessToken="+accessToken);
        System.out.println("refreshToken="+refreshToken);
        System.out.println("request.getHeader="+request.getHeader(HttpHeaders.AUTHORIZATION));
        //插件式配置请求参数（网址、请求参数、编码、client）
        Header[] headers = HttpHeader.custom().contentType(HttpHeader.Headers.APP_FORM_URLENCODED).authorization(request.getHeader(HttpHeaders.AUTHORIZATION)).build();
        HttpConfig config = HttpConfig.custom().headers(headers).url(refreshTokenUrl).map(map);
        token = HttpClientUtil.post(config);
        JSONObject jsonObj = JSON.parseObject(token);
//        log.info("===================token is:{}, jsonObj is:{}", token, jsonObj);

        String accessTokenNew = (String) jsonObj.get("access_token");
        String refreshTokenNew = (String) jsonObj.get("refresh_token");
        String loginName = (String) jsonObj.get("loginName");
        if (!PublicUtil.isEmpty(accessTokenNew) && !PublicUtil.isEmpty(refreshTokenNew) && !PublicUtil.isEmpty(loginName)) {
            // 更新本次token数据
// mod by wg 20181224 如果是用户刷新token，刷新成功后把原来的记录不再进行更新，直接删除掉，生成新的记录
//            UserTokenDto tokenDto = this.getByAccessToken(accessToken);
//            tokenDto.setStatus(AccUserTokenStatusEnum.ON_REFRESH.getStatus());
            AccUser accUser = accUserService.findUserInfoByLoginName(loginName);
            log.info("更新本次token数据,accUser={}",accUser);
            LoginAuthDto loginAuthDto = new LoginAuthDto(accUser.getUserId(), accUser.getLoginName(), accUser.getUserName(), accUser.getGroupId(),accUser.getGroupName());
//            this.updateUacUserToken(tokenDto, loginAuthDto);

            // 创建刷新token
            this.saveUserToken(accessTokenNew, refreshTokenNew, loginAuthDto, request, true, accessToken);
        }

        // add by wg 20190110 如果refresh token也过期，则删除掉user token表中对应的数据，防止该表脏数据过多
        String error = (String) jsonObj.get("error");
        String errorDesc = (String) jsonObj.get("error_description");
        if (PublicUtil.isNotEmpty(error) && error.equalsIgnoreCase("invalid_token")
                && PublicUtil.isNotEmpty(errorDesc) && errorDesc.contains("Invalid refresh token (expired)")) {
            Example example = new Example(AccUserToken.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("refreshToken", refreshToken);
            accUserTokenMapper.deleteByExample(example);
        }

        return token;
    }

    @Override
    public int batchUpdateTokenOffLine() {
        List<Long> idList = accUserTokenMapper.listOffLineTokenId();
        if (PublicUtil.isEmpty(idList)) {
            return 1;
        }
        Map<String, Object> map = Maps.newHashMap();
        map.put("status", 20);
        map.put("tokenIdList", idList);
        return accUserTokenMapper.batchUpdateTokenOffLine(map);
    }

    @Override
    public List<UserLoginInfoDto> getUserDeviceToken(Long userId) {
        Preconditions.checkArgument(userId != null, "用户id为空");

        return accUserTokenMapper.getUserDeviceToken(userId);
    }
}
